package it.tukano.swingdeco.containers;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 * A panel that can be resized dragging one edge
 * @author pgi
 */
public class ResizablePanel extends JPanel {

    private static final long serialVersionUID = 0;

    private final Edge edge;
    private final JLabel resizer = new JLabel();
    private final int dragAreaSize = 4;
    private final Cursor resizeCursor;
    private Point pressPoint = null;
    private Component view;

    /**
     * Initializes this resizable panel
     * @param edge the resizable edge of this panel
     */
    public ResizablePanel(Edge edge) {
        super(new BorderLayout());
        this.edge = edge;
        switch(edge) {
            case TOP:
            case BOTTOM:
                resizeCursor = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
                break;
            case LEFT:
            case RIGHT:
            default:
                resizeCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
                break;
        }
        addMouseListener(new MouseAdapter() {

            @Override
            public void mouseDragged(MouseEvent e) {
                ResizablePanel.this.handleDrag(e);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                ResizablePanel.this.handlePress(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                ResizablePanel.this.handleRelease(e);
            }
        });
        resizer.setCursor(resizeCursor);
        switch(edge) {
            case BOTTOM:
                add(resizer, BorderLayout.SOUTH);
                break;
            case LEFT:
                add(resizer, BorderLayout.WEST);
                break;
            case RIGHT:
                add(resizer, BorderLayout.EAST);
                break;
            case TOP:
                add(resizer, BorderLayout.NORTH);
                break;
        }
        resizer.setPreferredSize(new Dimension(dragAreaSize, dragAreaSize));
    }

    /**
     * Checks if the given component is added to the center
     * of this panel
     * @param c the component to check
     */
    public void setView(Component c) {
        add(c, BorderLayout.CENTER);
        view = c;
    }

    /**
     * Checks if this component contains the given one at the center
     * @param c the component to check
     * @return true if c has been added as the view of this container
     */
    public boolean hasView(Component c) {
        return c.equals(view);
    }

    /**
     * Compute and sets the best size for this
     */
    public void pack() {
        Dimension size = new Dimension();
        for (Component component : getComponents()) {
            Dimension pref = component.getPreferredSize();
            size.width = Math.max(pref.width, size.width);
            size.height = Math.max(pref.height, size.height);
        }
        size.width = Math.max(size.width, dragAreaSize);
        size.height = Math.max(size.height, dragAreaSize);
        setPreferredSize(size);
        getParent().invalidate();
        getParent().validate();
    }

    private void handleDrag(MouseEvent e) {
    }

    private void handlePress(MouseEvent e) {
        if(SwingUtilities.isLeftMouseButton(e)) {
            pressPoint = e.getPoint();
        } else {
            pressPoint = null;
        }
    }

    private void handleRelease(MouseEvent e) {
        if(pressPoint != null && SwingUtilities.isLeftMouseButton(e)) {
            int delta = computedelta(e.getPoint(), pressPoint);
            setPreferredSize(computeNewSize(delta));
            getParent().invalidate();
            getParent().validate();
            pressPoint = null;
        }
    }

    private int computedelta(Point last, Point first) {
        switch(edge) {
            case LEFT: return first.x - last.x;
            case RIGHT: return last.x - first.x;
            case TOP: return first.y - last.y;
            default:
            case BOTTOM: return last.y - first.y;
        }
    }

    private Dimension computeNewSize(int delta) {
        Dimension size = getSize();
        switch(edge) {
            case LEFT:
            case RIGHT:
                size.width += delta;
                break;
            case TOP:
            case BOTTOM:
                size.height += delta;
        }
        size.width = Math.max(size.width, dragAreaSize);
        size.height = Math.max(size.height, dragAreaSize);
        return size;
    }

    /**
     * Defines the draggable edge of a resizable panel
     */
    public static enum Edge {

        /**
         * The top edge
         */
        TOP,

        /**
         * The bottom edge
         */
        BOTTOM,

        /**
         * The left edge
         */
        LEFT,

        /**
         * The right edge
         */
        RIGHT
    }
}
